package biolabs.model.vis.j2d

import java.awt.{Graphics2D, Rectangle}
import java.awt.geom.{AffineTransform, Rectangle2D}
import java.net.URI

import scala.math.min

import biolabs.space.{Continuous2DPosition, Discrete2DPosition, GridShapedObject}
import biolabs.visual.Visualizer
import biolabs.visual.j2d.Java2DPainter

import com.kitfox.svg.SVGUniverse

/** Visualizer that displays an SVG image in place of a 2D grid object.
  * The image is centered around the object's center of gravity and scaled
  * so that it doesn't exceed the smallest rectangle containing the object.
  * (This does not mean that the image will not exceed the object's boundary.)
  * An additional scaling factor can be applied to shrink the image.
  *
  * @param imageURI absolute URI of the SVG image.
  * @param scale Additional scaling factor of the image for optional
  * shrinking of the image. Must be from the interval `(0,1]`.
  */
class GridShaped2DObjectSVGVisualizer(imageURI: URI, scale: Float = 1)
extends Visualizer[GridShapedObject[Discrete2DPosition, Continuous2DPosition], Java2DPainter] {

	if(scale <= 0 || scale > 1)
		throw new IllegalArgumentException("Scale must be from interval (0,1]. Found: " + scale)

	private val universe = new SVGUniverse()
	private val diagram = universe.getDiagram(imageURI)

	override def visualize(o: GridShapedObject[Discrete2DPosition, Continuous2DPosition]) =
		new Java2DPainter() {

			val span = spanToRect(o.getShape().getSpan())

			// Transforms the device so that the whole image
			// is drawn within the span of object's shape.
			val transform = {
				val cog = o.getShape().getCenterOfGravity()
				val (cx, cy) = (cog.getX()+0.5, cog.getY()+0.5)
				val halfW = min(cx - span.getMinX(), span.getMaxX() - cx) * scale
				val halfH = min(cy - span.getMinY(), span.getMaxY() - cy) * scale
				val targetViewRect = new Rectangle2D.Double(cx-halfW, cy-halfH, 2*halfW, 2*halfH)
				mapRect(diagram.getViewRect(), targetViewRect)
			}

			override def paint(g: Graphics2D) {
				// save the original transformation
				val savedTransform = g.getTransform()

				// set transformation and draw the image
				g.transform(transform)
				diagram.render(g)

				// restore the original transformation
				g.setTransform(savedTransform)
			}

			override def getBounds() = span
		}

	/** Conversion from `Pair[Discrete2DPosition, Discrete2DPosition]` to `Rectangle`. */
	private def spanToRect(span: biolabs.util.Pair[Discrete2DPosition, Discrete2DPosition]) = {
		val (p, size) = (span.a, span.b)
		new Rectangle(p.getX(), p.getY(), size.getX(), size.getY())
	}

	private def mapRect(from: Rectangle2D, to: Rectangle2D): AffineTransform = {
		// notice the reverse order of applying transformations

		// translate to the target position
		val tr = AffineTransform.getTranslateInstance(to.getX(), to.getY())

		// scale to the target size
		val scaleX = to.getWidth() / from.getWidth()
		val scaleY = to.getHeight() / from.getHeight()
		tr.scale(scaleX, scaleY)

		// translate the original rectangle to (0,0)
		tr.translate(-from.getX, -from.getY)

		tr
	}
}
